package Simulator;

import EcoSpeed.Alarme;
import EcoSpeed.SmartSystem;
import java.util.*;
import EcoSpeed.Utils;
import SimulatorStandard.StandardDriverKeyBoard;
import SimulatorStandard.StandardDriverLift;
import TimeLine.TimeLine;
import java.util.GregorianCalendar;

/**
 * Class Simulator
 */
public class Simulator extends Observable implements Observer {

    /**
     * Date courrant
     */
    private Date date;
    /**
     * TimeLine pour la gestion du temps qui passe
     */
    private TimeLine time;
    /**
     * Pour savoir si le mode par quota est activé
     */
    private boolean activQuota;
    /**
     * Pour savoir si on est a la 1er mise a jour
     */
    private boolean firstUpdate = true;
    /**
     * Liste des requetes en cours chez EcoSpeed
     */
    private Vector<Request> requestsEcoSpeedList;
    /**
     * Liste des requetes finit chez EcoSpeed
     */
    private Vector<Request> finishEcoSpeedRequestsList;
    /**
     * Liste des requetes en cours sur le system standard
     */
    private Vector<Request> requestsStandardList;
    /**
     * Liste des requetes terminé sur le system standard
     */
    private Vector<Request> finishStandardRequestsList;
    /**
     * Liste des claviers de saisie pour EcoSpeed
     */
    private Vector<DriverKeyboard> keyboards;
    /**
     * Liste des claviers de saisie pour le system standard
     */
    private Vector<Vector<StandardDriverKeyBoard>> stdKeyboards;
    /**
     * Liste des ascenseur standard
     */
    private Vector<StandardDriverLift> stdLifts;
    /**
     * Alarm de l'immeuble
     */
    private Alarme alarm;
    /**
     * System de quota
     */
    private Quota q;
    /**
     * SmartSystem pour la modification des paramètre
     */
    private SmartSystem sm;

    /**
     * Constructeur
     * @param keyboards clavier EcoSpeed
     * @param time TimeLine
     * @param alarm Alarm
     * @param sm SmartSystem
     * @param stdKeyboards clavier Standard
     * @param stdLifts Ascenseur standard
     */
    public Simulator(Vector<DriverKeyboard> keyboards, TimeLine time, Alarme alarm, SmartSystem sm, Vector<Vector<StandardDriverKeyBoard>> stdKeyboards, Vector<StandardDriverLift> stdLifts) {
        this.keyboards = keyboards;
        requestsEcoSpeedList = new Vector<Request>();
        finishEcoSpeedRequestsList = new Vector<Request>();
        requestsStandardList = new Vector<Request>();
        finishStandardRequestsList = new Vector<Request>();
        this.time = time;
        this.date = (new GregorianCalendar(2011, 00, 01, 00, 00, 00)).getTime();
        System.out.println(this.date);
        this.alarm = alarm;
        this.q = new Quota();
        this.activQuota = false;
        this.sm = sm;
        this.stdKeyboards = stdKeyboards;
        this.stdLifts = stdLifts;
    }

    /**
     * Set the value of activQuota
     * @param newVar the new value of activQuota
     */
    public void setActivQuota(boolean newVar) {
        activQuota = newVar;
    }

    /**
     * Get the value of activQuota
     * @return the value of activQuota
     */
    public boolean getActivQuota() {
        return activQuota;
    }

    /**
     * Get the value of requestsEcoSpeedList
     * @return the value of requestsEcoSpeedList
     */
    public Vector<Request> getRequestsEcoSpeedList() {
        return requestsEcoSpeedList;
    }

    /**
     * Get the value of finishEcoSpeedRequestsList
     * @return the value of finishEcoSpeedRequestsList
     */
    public Vector<Request> getFinishRequestsEcoSpeedList() {
        return this.finishEcoSpeedRequestsList;
    }

    /**
     * Get the value of finishStandardRequestsList
     * @return the value of finishStandardRequestsList
     */
    public Vector<Request> getFinishRequestsStandardList() {
        return this.finishStandardRequestsList;
    }

    /**
     * Ajoute une requete a la simulation
     * @param launchDate Date de lancement
     * @param startFloor Etage de debut
     * @param endFloor Etage de fin
     * @param weight Charge de la demande
     * @param urgent Urgence ou pas
     */
    public void addRequest(Date launchDate, int startFloor, int endFloor, int weight, boolean urgent) {
        Request temp = new Request();
        Request temp2 = new Request();
        temp.setLaunchDate(launchDate);
        temp2.setLaunchDate(launchDate);
        temp.setEndFloor(endFloor + Utils.NB_UNDERGROUND);
        temp2.setEndFloor(endFloor + Utils.NB_UNDERGROUND);
        temp.setStartFloor(startFloor + Utils.NB_UNDERGROUND);
        temp2.setStartFloor(startFloor + Utils.NB_UNDERGROUND);
        temp.setUrgent(urgent);
        temp2.setUrgent(urgent);
        temp.setWeight(weight);
        temp2.setWeight(weight);
        temp.setStatus(Request.TO_DO);
        temp2.setStatus(Request.TO_DO);
        this.requestsEcoSpeedList.add(temp);
        this.requestsStandardList.add(temp2);
    }

    /**
     * Change l'état de l'alarm
     */
    public void switchAlarm() {
        if (this.alarm.getStatut()) {
            this.alarm.disable();
        } else {
            this.alarm.enable();
        }
    }

    /**
     * Supprime toutes les requêtes.
     */
    public void clearAllRequests() {
        if (this.requestsEcoSpeedList != null) {
            this.requestsEcoSpeedList.clear();
        }
        if (this.requestsStandardList != null) {
            this.requestsStandardList.clear();
        }
        if (this.finishEcoSpeedRequestsList != null) {
            this.finishEcoSpeedRequestsList.clear();
        }
        if (this.finishStandardRequestsList != null) {
            this.finishStandardRequestsList.clear();
        }
    }

    /**
     * Methode de mise a jour suivant le pattern Observer
     * @param        arg0 L'objet observer
     * @param        arg1 les arguments de la mise a jour
     */
    public void update(Observable arg0, Object arg1) {
        if (arg0 instanceof EcoSpeed.Screen) {
            /* Si un screen a changer */
            //Recuperation du message
            EcoSpeed.PrintMessage mes = (EcoSpeed.PrintMessage) arg1;
            //Recuperation de la demande d'ascenseur
            Request req = null, tmp;
            Iterator it = this.requestsEcoSpeedList.iterator();
            while (it.hasNext() && req == null) {
                tmp = (Request) it.next();
                //Si la requete est en attente d'un ascenseur et part du bon étage
                if (tmp.getStatus() == Request.LIFT_WAITING_NUMBER && tmp.getStartFloor() == mes.idScreen) {
                    req = tmp;
                }
            }
            //Affectation de l'ascenseur a la demande
            //Si on a trouver la demande
            if (req != null) {
                req.setLift(mes.value);
                req.setStatus(Request.LIFT_WAITING);
                //Si il s'agit d'un urgence
                if (req.getUrgent()) {
                    Vector<Request> newRequests = new Vector<Request>();
                    Request newRequest = null;
                    //Recherche de toutes les requete de l'ascenseur selectionner
                    it = this.requestsEcoSpeedList.iterator();
                    while (it.hasNext()) {
                        tmp = (Request) it.next();
                        //Pour les requete qui ont l'ascenseur selectionner
                        if (tmp.getLift() == mes.value && tmp != req) {
                            //On recré automatiquemen des requete pour celle qui ne sont pas finit
                            if (tmp.getEndFloor() != req.getStartFloor()) {
                                newRequest = new Request();
                                newRequest.setLaunchDate(tmp.getLaunchDate());
                                newRequest.setEndFloor(tmp.getEndFloor());
                                newRequest.setStartFloor(req.getStartFloor());
                                newRequest.setStatus(Request.TO_DO);
                                newRequest.setUrgent(tmp.getUrgent());
                                newRequest.setWeight(tmp.getWeight());
                                newRequests.add(newRequest);
                                //On les fait sortir a l'étage de debut de la demande urgente
                                tmp.setEndFloor(req.getStartFloor());
                            }
                        }
                    }
                    it = newRequests.iterator();
                    while (it.hasNext()) {
                        tmp = (Request) it.next();
                        this.requestsEcoSpeedList.add(tmp);
                    }
                }
            }
        } else if (arg0 instanceof DriverLift) {
            /* Si un ascenseur à changer */
            //Recuperation de l'ascenseur
            DriverLift lift = (DriverLift) arg0;
            Request tmp;
            //Pour toutes les demandes
            for (int i = 0; i < this.requestsEcoSpeedList.size();) {
                tmp = this.requestsEcoSpeedList.get(i);
                //Seul les demandes en cours pour cet ascenseur
                if (tmp.getStatus() != 0 && tmp.getLift() == lift.getId()) {
                    //Les demandes qui doivent monté dans l'ascenseur
                    if (tmp.getStatus() == Request.LIFT_WAITING && tmp.getStartFloor() == lift.getFloor() && lift.doorOpened()) {
                        if (!lift.setWeight(lift.getWeight() + tmp.getWeight())) {
                            //Si on n'a pas réusi a rentré alors on relance
                            tmp.getLaunchDate().setTime(tmp.getLaunchDate().getTime() + 10000);
                            tmp.setStatus(Request.TO_DO);
                        } else {
                            tmp.setStatus(Request.IN_LIFT);
                        }
                    } //Les demandes qui doivent sortir de l'ascenseur
                    else if (tmp.getStatus() == Request.IN_LIFT && tmp.getEndFloor() == lift.getFloor() && lift.doorOpened()) {
                        //Les personnes sorte
                        lift.setWeight(lift.getWeight() - tmp.getWeight());
                        //Fin de la demande donc on l'historise
                        tmp.setEndDate((Date) this.date.clone());
                        this.finishEcoSpeedRequestsList.add(tmp);
                        this.requestsEcoSpeedList.remove(tmp);
                        this.setChanged();
                        this.notifyObservers();
                        i--;
                    }
                }
                i++;
            }
        } // Si un ascenseur standard à changé.
        else if (arg0 instanceof StandardDriverLift) {
            StandardDriverLift lift = (StandardDriverLift) arg0;
            Request tmp;
            for (int i = 0; i < this.requestsStandardList.size(); i++) {
                tmp = this.requestsStandardList.get(i);
                if (tmp.getStatus() == Request.LIFT_WAITING && tmp.getStartFloor() == lift.getFloor() && (tmp.getLift() == lift.getId() || tmp.getLift() == -1) && lift.doorOpened()) {
                    tmp.setLift(lift.getId());
                    tmp.setStatus(Request.IN_LIFT);
                    lift.addStop(tmp.getEndFloor());
                    if (!lift.setWeight(lift.getWeight() + tmp.getWeight())) {
                        tmp.setStatus(Request.TO_DO);
                    }
                }
                if (tmp.getStatus() == Request.IN_LIFT && tmp.getEndFloor() == lift.getFloor() && tmp.getLift() == lift.getId() && lift.doorOpened()) {
                    lift.setWeight(lift.getWeight() - tmp.getWeight());
                    tmp.setEndDate((Date) this.date.clone());
                    this.finishStandardRequestsList.add(tmp);
                    this.requestsStandardList.remove(tmp);
                }
            }
        } else {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    /**
     * Methode qui est apeller par le timeLine pour la generation des demandes
     */
    public void updateTimeLine() {
        Calendar cal = GregorianCalendar.getInstance();
        cal.setTime(this.date);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        this.date.setTime(this.date.getTime() + 1000);
        cal.setTime(this.date);
        //Si on a changé d'heure
        if (this.firstUpdate || this.activQuota && hour != cal.get(Calendar.HOUR_OF_DAY)) {
            //Génération des requete via Quota
            this.q.getRequests(date);
        }
        if (this.firstUpdate || hour != cal.get(Calendar.HOUR_OF_DAY)) {
            this.sm.updateZones();
        }
        //Lancement des requete pour EcoSpeed
        Request rq = null;
        //Pour chaque Action)
        for (int i = 0; i < this.requestsEcoSpeedList.size(); i++) {
            rq = this.requestsEcoSpeedList.get(i);
            //Que les Actions a faire et qu'on doit faire maintenant
            if (rq.getStatus() == Request.TO_DO && rq.getLaunchDate().before(this.date)) {
                rq.setStatus(Request.LIFT_WAITING_NUMBER);
                this.keyboards.get(rq.getStartFloor()).sendRequest(rq.getStartFloor(), rq.getEndFloor(), rq.getUrgent());
            }
        }
        //Lancement des requetes pour le system Standard
        Random randGenerator = new Random();
        int keyboardNumber;
        for (int i = 0; i < this.requestsStandardList.size(); i++) {
            rq = this.requestsStandardList.get(i);
            //Que les Action a faire et qu'on doit faire maintenant
            if (rq.getStatus() == Request.TO_DO && rq.getLaunchDate().before(this.date)) {
                rq.setStatus(Request.LIFT_WAITING);
                int sens = (rq.getStartFloor() < rq.getEndFloor() ? 1 : -1);
                if (randGenerator.nextInt(100) >= 60) {
                    //30% des personnes sont précé
                    for (int j = 0; j < 6; j++) {
                        this.stdKeyboards.get(j).get(rq.getStartFloor()).sendRequest(rq.getStartFloor(), sens);
                    }
                    rq.setLift(-1);
                } else {
                    keyboardNumber = randGenerator.nextInt(6);
                    rq.setLift(keyboardNumber);
                    this.stdKeyboards.get(keyboardNumber).get(rq.getStartFloor()).sendRequest(rq.getStartFloor(), sens);
                }
            }
        }
        this.firstUpdate = false;
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * Methode qui multipli par 2 la vitesse du timeLine
     */
    public void increaseTimeLineSpeed() {
        time.setRatio(time.getRatio() * 2);
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * Methode qui divise par 2 la vitesse du timeLine
     */
    public void decreaseTimeLineSpeed() {
        time.setRatio(time.getRatio() / 2);
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * Methode qui start le TimeLine
     */
    public void start() {
        time.start();
    }

    /**
     * Methode qui stop le TimeLine
     */
    public void stop() {
        time.stop();
    }

    /**
     * Methode qui reset le Temps
     */
    public void reset() {
        this.firstUpdate = true;
        this.clearAllRequests();
        this.clearQuota();
        this.date = (new GregorianCalendar(2011, 00, 01, 00, 00, 00)).getTime();
        this.sm.reset();
        this.setChanged();
        this.notifyObservers();
        this.firstUpdate = true;
        Iterator it = this.stdLifts.iterator();
        while (it.hasNext()) {
            ((StandardDriverLift) it.next()).reset();
        }
        if (this.time.isStarted()) {
            this.time.stop();
            this.time.start();
        }
    }

    /**
     * Accesseur de la date
     * @return la date courante
     */
    public Date getDate() {
        return this.date;
    }

    /**
     * Accesseur de la date
     * @param d la nouvelle date
     */
    public void setDate(Date d) {
        this.date = d;
        this.sm.setDate(d);
    }

    /**
     * Donne la vitesse de traitement
     * @return la vitesse de traitement
     */
    public int getSpeed() {
        return this.time.getRatio();
    }

    /**
     * Ajout une zone de quota
     * @param startHour heure de debut de la zone
     * @param endHour heure de fin de la zone
     * @param startWeekDay jour de debut de la zone
     * @param endWeekDay jour de fin de la zone
     * @param dailyRequestNumber Nombre de requete par jour
     * @param startCallerZone etage de debut de la zone d'appel
     * @param endCallerZone etage de fin de la zone d'appel
     * @param startTargetZone etage de debut de la zone de destination
     * @param endTargetZone etage de fin de la zone de destination
     */
    public void addQuotaZone(int startHour, int endHour, int startWeekDay, int endWeekDay, int startCallerZone, int endCallerZone, int startTargetZone, int endTargetZone, int dailyRequestNumber) {
        q.addZone(startHour, endHour, startWeekDay, endWeekDay, dailyRequestNumber, startCallerZone, endCallerZone, startTargetZone, endTargetZone, this);
    }

    /**
     * Netoye les zone de quota
     */
    public void clearQuota() {
        q.clear();
    }

    /**
     * Méthode de chargement des statistiques depuis l'IHM.
     * @param stats nouvelles statistics
     */
    public void loadStatistics(int[][][] stats) {
        this.sm.getStats().setStatistics(stats);
    }
}
